package utils;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.security.PublicKey;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
import java.util.zip.ZipOutputStream;

import security.Hash;
import security.crypto.AssimEncryption;
import security.keyManagement.KeyStoreManager;


public class SignatureUtils {

    /**
     * Reads the given file, and returns the data in Base64 format.
     * 
     * @param file The file to be read.
     * @return The String, containing the data in Base64 format
     */
    public static String readFileToString(File file) {

        InputStream is;
        try {
            is = new FileInputStream(file);
            //InputStreamReader isr = new InputStreamReader(is, "UTF8");
            //InputStreamReader isr = new InputStreamReader(is);


            // Get the size of the file
            long length = file.length();

            if (length > Integer.MAX_VALUE) {
                // File is too large
            }

            // Create the byte array to hold the data
            byte[] bytes = new byte[(int)length];

            // Read in the bytes
            int offset = 0;
            int numRead = 0;
            while (offset < bytes.length
                    && (numRead=is.read(bytes, offset, bytes.length-offset)) >= 0) {
                offset += numRead;
            }

            // Ensure all the bytes have been read in
            if (offset < bytes.length) {
                throw new IOException("Could not completely read file "+file.getName());
            }

            // Close the input stream and return a Base64 String
            is.close();

            return Base64Processing.encodeData(bytes);
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }

        return null;
    }

    /**
     * Writes the given String (non-Base64 format) to a file,
     * whose name is also specified.
     * 
     * @param filePath The name of the file to write the String into.
     * @param data The data to be written (non-Base64).
     * 
     * TESTED
     */
    public static void writeStringToFile(String filePath, String data) {

        // Decode the data from Base64 to a byte[]
        //byte[] byteData = Base64Processing.decodeData(data);

        // Get byte[] from String
        byte[] byteData = data.getBytes();

        // Write it to the given File
        FileOutputStream fos;
        try {
            fos = new FileOutputStream(filePath);

            fos.write(byteData);

            fos.close();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * Compresses the specified files into a Zip file. 
     * 
     * @param archName The name for the desired archive file.
     * @param filePaths The paths for the files to be archived.
     * 
     * TESTED
     */
    public static void compressFiles(String archName, String[] filePaths) {
        FileOutputStream dest;
        try {
            dest = new FileOutputStream(archName);

            int bytesRead;
            byte[] buffer = new byte[4096];

            ZipOutputStream out = new ZipOutputStream(new BufferedOutputStream(dest));

            for (String file : filePaths) {
                File f = new File(file);
                FileInputStream in = new FileInputStream(f); // Stream to read file
                ZipEntry entry = new ZipEntry(f.getName()); // Make a ZipEntry
                out.putNextEntry(entry); // Store entry
                while ((bytesRead = in.read(buffer)) != -1)
                    out.write(buffer, 0, bytesRead);
                in.close(); 
            }
            out.close();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * Extracts a file from the supplied Zip Archive, and returns a String
     * with its contents.
     * 
     * @param path Path to the archive file.
     * @param name Name of the file to extract
     * @return String containing the file (non-Base64)
     * 
     * TESTED
     */
    private static String extractFileFromZip(String path, String name) {

        try {
            // Open the Zip Archive
            FileInputStream fis = new FileInputStream(path);
            ZipInputStream zin = new ZipInputStream(new BufferedInputStream(fis));

            // Extract the file to the same directory
            ZipEntry entry;
            BufferedOutputStream dest;

            // Set the name for the extracted files
            String extName = (new File(path)).getParent() + File.separator + "zip-temp";

            while((entry = zin.getNextEntry()) != null) {
                // If the entry matches the supplied name
                if(entry.getName().equals(name)) {
                    int count;
                    byte data[] = new byte[2048];
                    // write the files to the disk
                    FileOutputStream fos = new FileOutputStream(extName);
                    dest = new BufferedOutputStream(fos, 2048);
                    while ((count = zin.read(data, 0, 2048)) 
                            != -1) {
                        dest.write(data, 0, count);
                    }
                    dest.flush();
                    dest.close();
                }
            }

            // Read the file to a String
            File f = new File(extName);
            String fileRead = new String(Base64Processing.decodeData(readFileToString(f)));

            // Erase the Extracted File
            f.delete();

            return fileRead;
        } catch (IOException e) {
            e.printStackTrace();
        }

        return null;
    }

    /**
     * Extracts the document in a signature archive, encoded to 
     * Base64 format.
     * 
     * @param path Path to the archive file.
     * @return The document file encoded to Base64
     */
    public static String extractB64DocumentFromZip(String path) {

        String name = (new File(path)).getName().split("-signed.zip")[0];

        String res = Base64Processing.encodeData((extractFileFromZip(path, name).getBytes()));

        return res;
    }

    public static String extractSignatureStructFromZip(String path) {
        String name = (new File(path)).getName().split("-signed.zip")[0] + ".certcop";

        String res = extractFileFromZip(path, name);

        return res;
    } 

    /**
     * Get a field from the signature structure.
     * 
     * @param signaturePath Path to the signature archive file.
     * @param index Index of the intended field.
     * @return A string containing that field.
     */
    private static String getSignatureField(String signaturePath, Integer index) {
        // Extract the Signature
        File f = new File(signaturePath);
        String signFile = f.getName().split("-signed.zip")[0] + ".certcop";
        String fileExtracted = extractFileFromZip(signaturePath, signFile);

        // Get the Selected Field
        return fileExtracted.split("\n")[index].trim();
    }
    
    public static String getSignatureFieldFromString(String archString, Integer index) {
        return archString.split("\n")[index].trim();
    }

    public static String[] getSigSerialAndSignedValue(String sigArchFile) {

        String[] res = new String[] {getSignatureField(sigArchFile, 1), getSignatureField(sigArchFile, 8)};

        return res;
    }

    /**
     * Takes a Signature File Data, and strips the Signed Hash value.
     * (Effectively removes the 9th value)
     * 
     * @param signature
     * @return The Signature Structure without its signature
     * 
     * TESTED
     */
    private static String stripSignedHash(String signature) {
        String res = "";
        int i = 0;

        for(String line : signature.split("\n")) {
            // It's the last field, and it doesn't have a trailing '\n'
            if (i == 7) {
                res += line;
                break;
            }
            res += line + "\n";
            i++;
        }

        return res;
    }

    /**
     * Determines whether or not a signature is VERICOP verified
     * or not.
     * 
     * @param filePath The path to the archive file.
     * @return The result
     * 
     * TESTED
     */
    public static Boolean isSignatureLocal(String filePath) {

        // Get the Signature's 6th field
        String vericopField = getSignatureField(filePath, 5);

        // Determine the result
        Boolean result = new Boolean(vericopField);

        return result;
    }

    /**
     * Validates an archive file locally.
     * 
     * @param filePath Path to the archive file
     * @param certcopPublicKey Public Key of the CERTCOP server, used to validate the archive
     * @return The result
     * 
     *  TESTED
     */
    public static Boolean validateLocalSignature(String filePath, PublicKey certcopPublicKey) {

        // Get the Hash Value in the Signature
        String hashValue =  getSignatureField(filePath, 0);

        // Calculate Document Hash
        String docName = (new File(filePath)).getName().split("-signed.zip")[0];
        String docData = extractB64DocumentFromZip(filePath);
        String docHash = Hash.getStringHash(docData);

        // Determine the result
        Boolean hashResult = hashValue.equals(docHash);

        // If the signature doesn't match, return false
        if(!hashResult) {
            return false;
        }

        // Get the Signed value of the signature (9th value)
        String sigCiphValue = getSignatureField(filePath, 8);

        // Get the Signature structure and calculate its Hash
        String sigFileName = docName + ".certcop";
        String signatureStruct = stripSignedHash(extractFileFromZip(filePath, sigFileName));
        String sigStructHashValue = Hash.getStringHash(signatureStruct);

        // Validate the Hash value against its ciphered counterpart
        Boolean sigHashResult = AssimEncryption.validateSignature(sigStructHashValue, sigCiphValue, certcopPublicKey);

        return sigHashResult;
    }
    
    public static Boolean validateStringSignatureStructure(String document, String signatureStructure, PublicKey pubKey) {
        // Get the Hash Value in the Signature
        String hashValue =  getSignatureFieldFromString(signatureStructure, 0);

        // Calculate Document Hash
        String docHash = Hash.getStringHash(document);

        // Determine the result
        Boolean hashResult = hashValue.equals(docHash);

        // If the signature doesn't match, return false
        if(!hashResult) {
            return false;
        }

        // Get the Signed value of the signature (9th value)
        String sigCiphValue = getSignatureFieldFromString(signatureStructure, 8);

        // Get the Signature structure and calculate its Hash
        String sigStruct = stripSignedHash(signatureStructure);
        String sigStructHashValue = Hash.getStringHash(sigStruct);

        // Validate the Hash value against its ciphered counterpart
        Boolean sigHashResult = AssimEncryption.validateSignature(sigStructHashValue, sigCiphValue, pubKey);

        return sigHashResult;
    }
    
    /**
     * Returns whether the specified file is a valid signature archive.
     * 
     * @param filepath The path to the file to check.
     * @return Whether or not the file is a valid signature archive.
     */
    public static Boolean isSignatureFile(String filepath) {

        try {
            File f = new File(filepath);
            String[] split = f.getName().split("-signed.zip");
            //            if(split.length < 2) {
            //                return false;
            //            }
            String signedFile = split[0];

            // Open the Zip Archive
            FileInputStream fis = new FileInputStream(f);
            ZipInputStream zin = new ZipInputStream(new BufferedInputStream(fis));

            // Iterate through the file contents
            ZipEntry entry;
            while((entry = zin.getNextEntry()) != null) {
                if(!(entry.getName().equals(signedFile) || entry.getName().equals(signedFile + ".certcop")))
                    return false;
            }
        } catch (IOException e) {
            e.printStackTrace();
            return false;
        }

        return true;
    }

    /**
     * @param args
     * @throws IOException
     */
    public static void main(String[] args) throws IOException {
        //String fPath = "C:\\Users\\raVemjr\\Documents\\AIAC\\Projecto\\Playground\\AIAC.txt";
        String fPath = "/home/ravemjr/script1.awk-signed.zip";
//        File file = new File(fPath);

        //        // Calculate the hash and write to file
        //        String document = readFileToString(file);
        //        String docHash = Hash.getStringHash(document);
        //        String certFilePath = fPath + ".certcop";
        //        String data = docHash + "\n3128537126836\nCarlos Simões\nPresidente IST\n37771203179\nfalse\n15\n13/09/2011 15:43:22";
        //        String sigStructSignature = Hash.getStringHash(data);
        //        writeStringToFile(certFilePath, data + "\n" + sigStructSignature);
        //        compressFiles(fPath+"-signed.zip", new String[] {fPath, fPath + ".certcop"});

        KeyStoreManager keyStoreManager = new KeyStoreManager(null, false, false);

        Boolean res = validateLocalSignature(fPath, keyStoreManager.getCertcopPublicKey());
        //String sValue = extractFileFromZip(fPath + "-signed.zip", "AIAC.txt.certcop");

        System.out.println("Signature is " + (res ? "valid" : "invalid"));

        //        System.out.println(file.getParent());
        //
        //        String res = readFileToString(file);
        //        
        //        System.out.println(res);
        //
        //        writeStringToFile("C:\\Users\\raVemjr\\Desktop\\AIAC.txt.jcp", res);
        //
        //        String[] files = {"C:\\Users\\raVemjr\\Desktop\\AIAC.txt", "C:\\Users\\raVemjr\\Desktop\\AIAC.txt.jcp"};
        //
        //        compressFiles("C:\\Users\\raVemjr\\Desktop\\AIAC.certcop.zip", files);
        //        
        //        res = extractFileFromZip("C:\\Users\\raVemjr\\Desktop\\AIAC.certcop.zip", "AIAC.txt");
        //        
        //        //System.out.println(res);
        //        System.out.println(isSignatureLocal("C:\\Users\\raVemjr\\Desktop\\AIAC2.txt-signed.zip"));
    }

}
